// ***********************************************************
//
//   CCodeCounter.cpp - main class handling common features of CodeCount
//
//   REVISION  DATE     AUTHOR		MODIFICATION
//   Rev-1    Fall 08   T8			Original author, CSSE
//   Rev-2    12/23/08  RP, VN		Fixed and integrated code
//   Rev-3	  May 09	RP, VN, T2	Fixed comparison and function 
//   Rev-4    04/29/09  VN			Mod042909AS: Included changes provided by Aaron Shumate of NGC
// ***********************************************************

#include "CAdaCounter.h"

#define EXCLUDE "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$:"

CAdaCounter::CAdaCounter()
{
	classtype = ADA;
	language_name = "Ada";

	file_extension.push_back(".ada");
	file_extension.push_back(".a");
	file_extension.push_back(".adb");
	file_extension.push_back(".ads");

	LineCommentStart.push_back("--");

	QuoteStart = "\"";
	QuoteEnd = "\"";
	QuoteEscapeRear = '\"';

	directive.push_back("pragma");
	directive.push_back("controlled");
	directive.push_back("elaborate");
	directive.push_back("inline");
	directive.push_back("interface");
	directive.push_back("list");
	directive.push_back("memory_size");
	directive.push_back("optimize");
	directive.push_back("pack");
	directive.push_back("page");
	directive.push_back("priority");
	directive.push_back("shared");
	directive.push_back("storage_unit");
	directive.push_back("suppress");
	directive.push_back("system_name");

	data_name_list.push_back("procedure");
	data_name_list.push_back("function");
	data_name_list.push_back("package");
	data_name_list.push_back("task");
	data_name_list.push_back("generic");
	data_name_list.push_back("body");
	data_name_list.push_back("private");
	data_name_list.push_back("separate");
	data_name_list.push_back("constant");
	data_name_list.push_back("type");
	data_name_list.push_back("subtype");
	data_name_list.push_back("array");
	data_name_list.push_back("record");
	data_name_list.push_back("access");
	data_name_list.push_back("declare");
	data_name_list.push_back("renames");
	data_name_list.push_back("limited");
	data_name_list.push_back("use");
	data_name_list.push_back("with");
	data_name_list.push_back("new");

	exec_name_list.push_back("begin");
	exec_name_list.push_back("end");
	exec_name_list.push_back("goto");
	exec_name_list.push_back("if");
	exec_name_list.push_back("else");
	exec_name_list.push_back("elsif");
	exec_name_list.push_back("loop");
	exec_name_list.push_back("case");
	exec_name_list.push_back("when");
	exec_name_list.push_back("others");
	exec_name_list.push_back("exit");	
	exec_name_list.push_back("exception");
	exec_name_list.push_back("accept");	
	exec_name_list.push_back("entry");
	exec_name_list.push_back("return");
	exec_name_list.push_back("raise");
	exec_name_list.push_back("select");
	exec_name_list.push_back("abort");
	exec_name_list.push_back("terminate");
	exec_name_list.push_back("delay");

	cmplx_preproc_list.push_back("pragma");
	cmplx_preproc_list.push_back("controlled");
	cmplx_preproc_list.push_back("elaborate");
	cmplx_preproc_list.push_back("inline");
	cmplx_preproc_list.push_back("interface");
	cmplx_preproc_list.push_back("list");
	cmplx_preproc_list.push_back("memory_size");
	cmplx_preproc_list.push_back("optimize");
	cmplx_preproc_list.push_back("pack");
	cmplx_preproc_list.push_back("page");
	cmplx_preproc_list.push_back("priority");
	cmplx_preproc_list.push_back("shared");
	cmplx_preproc_list.push_back("storage_unit");
	cmplx_preproc_list.push_back("suppress");
	cmplx_preproc_list.push_back("system_name");

	cmplx_cond_list.push_back("if");
	cmplx_cond_list.push_back("else");
	cmplx_cond_list.push_back("elsif");
	cmplx_cond_list.push_back("loop");
	cmplx_cond_list.push_back("case");
	cmplx_cond_list.push_back("when");

	//cmplx_calc_list
	cmplx_calc_list.push_back("+");
	cmplx_calc_list.push_back("-");
	cmplx_calc_list.push_back("*");
	cmplx_calc_list.push_back("/");
	cmplx_calc_list.push_back("mod");
	cmplx_calc_list.push_back("rem");

	//cmplx_logic_list
	cmplx_logic_list.push_back("=");
	cmplx_logic_list.push_back("<");
	cmplx_logic_list.push_back(">");
	cmplx_logic_list.push_back("/=");
	cmplx_logic_list.push_back(">=");
	cmplx_logic_list.push_back("<=");
	cmplx_logic_list.push_back("&");
	cmplx_logic_list.push_back("and");
	cmplx_logic_list.push_back("or");
	cmplx_logic_list.push_back("xor");
	cmplx_logic_list.push_back("in");
	cmplx_logic_list.push_back("not in");

	//cmplx_assign_list
	cmplx_assign_list.push_back(":=");

	//log_func_list

	//trig_func_list

	//math_func_list
}

// Method: ReplaceQuote(...)
// Description: handle special case for Ada literal strings
// -----------------------------------------
// Revision  Date     Author  Modification
// Rev-1     03/20/09   VN      Original author
int CAdaCounter::ReplaceQuote(string &strline, size_t &idx_start, bool &contd, char &CurrentQuoteEnd)
{
	size_t idx = 0;
	while(true)
	{
		idx = strline.find("'\"'", idx); // replace all '"' by '$'
		if (idx != string::npos) strline.replace(idx, 3, 3, '$');
		else break;
	}
	return CCodeCounter::ReplaceQuote(strline, idx_start, contd, CurrentQuoteEnd);
}

int CAdaCounter::CountDirectiveSLOC(filemap* fmap, results* result, filemap* fmapBak)
{
	bool contd = false, trunc_flag = false;
	size_t idx, strSize;
	if(classtype == DATAFILE) return 0;
//	int cnt = 0;
//	string  exclude = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$";
	string strDirLine = "";
	filemap::iterator itfmBak = fmapBak->begin();

	for (filemap::iterator iter = fmap->begin(); iter!=fmap->end(); iter++, itfmBak++)
	{
		if (CUtil::CheckBlank(iter->line)) continue;

		int cnt=0;
		CUtil::CountTally(" " + iter->line, directive, cnt, 1, EXCLUDE, "", "", &directive_count, result->duplicate);

		if (!contd)
		{	// if not a continuation of a previous directive
			for(vector<string>::iterator viter = directive.begin(); viter != directive.end(); viter++)
			{
				if ( ((idx = CUtil::FindKeyword(iter->line, *viter)) != string::npos)
					&& idx == 0)	//SJB - Merged bug fix for considering only stand-alone keywords
									//e.g. package should not be considered a directive (only 'pack' is)
				{
					contd = true;
					break;
   		        }
        	}
			if (contd)
			{
				strSize = CUtil::TruncateLine(itfmBak->line.length(), 0, result->lsloc_truncate, trunc_flag);
				if (strSize > 0)
					strDirLine = itfmBak->line.substr(0, strSize);
				result->directive_lines[PHY]++;
			}	// incrementing the physical directive lines
 		}
		else
		{	// continuation of a previous directive
			strSize = CUtil::TruncateLine(itfmBak->line.length(), strDirLine.length(), result->lsloc_truncate, trunc_flag);
			if (strSize > 0)
				strDirLine += "\n" + itfmBak->line.substr(0, strSize);
			result->directive_lines[PHY]++; 		// incrementing the physical lines for a directive
		}
	
		if  (contd)
		{	// if a directive or continuation of a directive
			if (iter->line[iter->line.length()-1] != ',' && iter->line[iter->line.length()-1] != '\\')
			{
				contd = false;
				result->directive_lines[LOG]++;
				result->addSLOC(strDirLine, trunc_flag);  //add another Logical directive line, should also have type
			}	// if no continuation symbol found
			iter->line = "";
		}
	}
	return 1;
}

// Special Processing(w/ some revision to logic)
// Data Processing(works the same as before)
// Exec Processing(works the same as before)
// IMPORTANT NOTE: all the  blank lines +
//							whole line comments +
//							line with compiler directive
//				   should have been blanked from filemap by previous processing
//                 before reaching this function
// fmapBak is same as fmap except it contains unmodified quoted strings
int CAdaCounter::LanguageSpecificProcess(filemap* fmap, results* result, filemap* fmapBak)
{
	bool		blank_flag			= false;
	bool		begin_paren_flag	= false;
	bool		end_paren_flag		= false;
	int			paren_count			= 0	   ;
	bool		data_flag			= false;
	bool		data_continue		= false;
	bool		found_brace			= false;
	int			num_FOR_IF_WHILE	= 0	   ;
	int			num_terminators		= 0	   ;
	bool		SLOC_end_flag		= false;
	bool		function_flag		= false;
	bool		found_accept		= false;
	string		strLSLOC			= "";
	string		strLSLOCBak			= "";
	int cnt;

	filemap::iterator fit, fitbak;
	string line, lineBak, tmp;

	// two strings used for string match
//	string  exclude =
//			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$";
	string	special = "[]()+/-*<>=,@&~!^?:%{}";

	// Team 8: diffing function section
	int l_paren_cnt = 0;
	bool l_foundblock, found_forifwhile, found_end, found_type, found_is;
	//char prev_char = 0;
	l_foundblock = found_forifwhile = found_end = found_is = false;
	// Team 8: end diffing function section

	for (fit = fmap->begin(), fitbak = fmapBak->begin(); fit != fmap->end(); fit++, fitbak++)
	{
		// insert blank at the beginning(for searching keywords)
		line = ' ' + fit->line;
		lineBak = ' ' + fitbak->line;

		if (CUtil::CheckBlank(line))
		{
			blank_flag = true;	// the line is either blank/whole line comment/compiler directive
			continue;
		}
		else
		{
			blank_flag = false;	// should be processed by this func
		}

		if (!blank_flag)
		{	// blank line means blank_line/comment_line/directive

			// Team 8: Modified to support diff'ing function
			// Call SLOC function to detect logical SLOC and add to result
			LSLOC(result, line, lineBak, strLSLOC, strLSLOCBak, l_paren_cnt, l_foundblock,
				found_forifwhile, found_end, found_type, found_is, found_accept);
			// End of diff'ing function modification

			cnt = 0;

			CUtil::CountTally(line, data_name_list, cnt, 1, EXCLUDE, "", "", &data_name_count, result->duplicate);

			if (cnt > 0) // Need to check also if the data line continues
			{
				result->data_lines[PHY] += 1;
			}
			else
			{
				result->exec_lines[PHY] += 1;
			}
		}
            
	}	//for(fit)
	return 1;
}

// Method: FoundSLOC(...)
// Description: this method is called after a logical SLOC is determined.
//              The method adds LSLOC to the result, increase counts, and reset variables.
// -----------------------------------------
// Revision  Date     Author  Modification
// Rev-1     5/24/09  VN      Added, Mod052409VN
void CAdaCounter::FoundSLOC(results* result, string &strLSLOC, string &strLSLOCBak, bool &found_block, bool &found_forifwhile,
							bool &found_end, bool &found_type, bool &found_is, bool &found_accept, bool &trunc_flag)
{
	// Add to the list for comparison purpose
	result->addSLOC(CUtil::TrimString(strLSLOCBak), trunc_flag);
	
	// Determine logical type, data declaration or executable
	int cnt = 0;
	CUtil::CountTally(strLSLOC, data_name_list, cnt, 1, EXCLUDE, "", "", &data_name_count, result->duplicate);
	if (cnt > 0) {
		result->data_lines[LOG] += 1;
	} else {
		result->exec_lines[LOG] += 1;
	}

	// Reset all variables whenever a new statement/logical SLOC is found
	strLSLOC = "";
	strLSLOCBak = "";
	found_block = false;
	found_forifwhile = false;
	found_end = false;
	found_type = false;
	found_is = false;
	found_accept = false;
}

// Method: LSLOC(...)
// Description: determine and extract logical SLOC to place in the result variable 
//              using addSLOC function. Each time the addSLOC function is called,
//              a new logical SLOC is added. This function assumes that the directive
//              is handled before it is called.
// -----------------------------------------
// Revision  Date     Author  Modification
// Rev-1     12/1/08  T8      CS590 team added
// Rev-2     5/24/09  VN      Rewrote the whole function, Mod052409VN
void CAdaCounter::LSLOC(results* result, string line, string lineBak, string &strLSLOC, string &strLSLOCBak, int &paren_cnt,
						bool &found_block, bool &found_forifwhile, bool &found_end, bool &found_type, bool &found_is, bool &found_accept)
{
	string keywordchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_$";
	size_t start = 0; //starting index of the working string
	size_t i = 0, tempi, strSize;
	string templine = CUtil::TrimString(line);
	string tmp;
	bool trunc_flag = false;

	for (i = 0; i < line.length(); i++) // there may be more than 1 logical SLOC in this line
	{
		switch (line[i])
		{
		case ';':
			if (paren_cnt > 0)
				break;
			if (!found_end)
			{
				strSize = CUtil::TruncateLine(i - start, strLSLOC.length(), result->lsloc_truncate, trunc_flag);
				if (strSize > 0)
				{
					strLSLOC += line.substr(start, strSize);
					strLSLOCBak += lineBak.substr(start, strSize);
				}
				FoundSLOC(result, strLSLOC, strLSLOCBak, found_block, found_forifwhile, 
					found_end, found_type, found_is, found_accept, trunc_flag);
			} else {
				found_end = false; //end xxx;				
				found_block = false;
				found_is = false;
				found_forifwhile = false;
				found_type = false;
				found_accept = false;

				strLSLOC = "";
				strLSLOCBak = "";
			}

			start = i + 1;
			break;
		case '(':
			if (found_type)
				found_type = false;
			paren_cnt++;
			break;
		case ')':
			paren_cnt--;
			break;
		}

		// Continue the following processing only if line[i] is not in a middle of a word
		if (keywordchars.find(line[i]) != string::npos && i < line.length() - 1) continue;

		//if it ends in xxx, then it has already been counted, so ignore it
		tmp = "xxx " + CUtil::TrimString(line.substr(start,i+1));
		if (CUtil::FindKeyword(tmp, "end",0,TO_END_OF_STRING,false) != string::npos)
		{
			found_end = true;
		}

		if (!found_end)
		{
			// VN: 'begin' is ignored because it's counted with procedure, function, etc. already
			// This may ignore the 'standalone' block that starts with 'declare' or only 'begin'
//			if (CUtil::FindKeyword(tmp, "begin",0,TO_END_OF_STRING,false) != string::npos)
//			{
//				if (!found_is) // ignore '... is begin'
//				{
//					//found a SLOC
//					strLSLOC += line.substr(start, i - start + 1);
//					strLSLOCBak += lineBak.substr(start, i - start + 1);
//					FoundSLOC(result, strLSLOC, strLSLOCBak, found_block, found_forifwhile, 
//							found_end, found_type, found_is, found_accept); 
//					start = i+1;
//					continue;
//				}
//				else
//					found_is = false;
//			}
			if (!found_forifwhile)
			{
				if (CUtil::FindKeyword(tmp, "for",0,TO_END_OF_STRING,false) !=string::npos ||
					CUtil::FindKeyword(tmp, "while",0,TO_END_OF_STRING,false) !=string::npos ||
					CUtil::FindKeyword(tmp, "if",0,TO_END_OF_STRING,false) !=string::npos ||
					CUtil::FindKeyword(tmp, "elsif",0,TO_END_OF_STRING,false) !=string::npos)
				{
					found_forifwhile = true;
				}

				// 'exception' is removed because it is not counted, Mod052409VN
				if ( CUtil::FindKeyword(tmp, "loop",0,TO_END_OF_STRING,false) != string::npos)
				{
					//found a SLOC
					strSize = CUtil::TruncateLine(i + 1 - start, strLSLOC.length(), result->lsloc_truncate, trunc_flag);
					if (strSize > 0)
					{
						strLSLOC += line.substr(start, strSize);
						strLSLOCBak += lineBak.substr(start, strSize);
					}
					FoundSLOC(result, strLSLOC, strLSLOCBak, found_block, found_forifwhile,
						found_end, found_type, found_is, found_accept, trunc_flag);

					start = i+1;
					continue;
				}
			}
			else if (CUtil::FindKeyword(tmp, "loop",0,TO_END_OF_STRING,false) != string::npos ||
				CUtil::FindKeyword(tmp, "then",0,TO_END_OF_STRING,false) != string::npos ||
				CUtil::FindKeyword(tmp, "record",0,TO_END_OF_STRING,false) != string::npos) // for..use..record, Mod052409VN
			{
				//found a SLOC
				strSize = CUtil::TruncateLine(i + 1 - start, strLSLOC.length(), result->lsloc_truncate, trunc_flag);
				if (strSize > 0)
				{
					strLSLOC += line.substr(start, strSize);
					strLSLOCBak += lineBak.substr(start, strSize);
				}
				FoundSLOC(result, strLSLOC, strLSLOCBak, found_block, found_forifwhile,
					found_end, found_type, found_is, found_accept, trunc_flag);
				start = i+1;
				continue;
			}
			
			//similarly, check for procedure, task, function - it ends with 'is' keyword
			// procedure ... is...
			// package ... is ...
			if (!found_block)
			{
				if (CUtil::FindKeyword(tmp, "procedure",0,TO_END_OF_STRING,false) !=string::npos ||
					CUtil::FindKeyword(tmp, "function",0,TO_END_OF_STRING,false) !=string::npos ||
					CUtil::FindKeyword(tmp, "package",0,TO_END_OF_STRING,false) !=string::npos ||
					CUtil::FindKeyword(tmp, "task",0,TO_END_OF_STRING,false) !=string::npos ||
					CUtil::FindKeyword(tmp, "case",0,TO_END_OF_STRING,false) !=string::npos ||
					CUtil::FindKeyword(tmp, "protected",0,TO_END_OF_STRING,false) !=string::npos)
				{
					found_block = true;
				}
			}
			else // procedure...is...
			{
				// The 'if' statement below attempts to 'alleviate' the issue with
				// procedure Swap is new Exchange(Elem => Integer);
				// procedure Test1 is begin end Test1;
				// Only add new SLOC if 'is' is at the end of line and follows 'procedure', etc.
				// Importance: this implementation may not be complete!
				tempi = CUtil::FindKeyword(templine, "is",0,TO_END_OF_STRING,false);
				if (tempi == templine.length() - 2)
				{
					strSize = CUtil::TruncateLine(i + 1 - start, strLSLOC.length(), result->lsloc_truncate, trunc_flag);
					if (strSize > 0)
					{
						strLSLOC += line.substr(start, strSize);
						strLSLOCBak += lineBak.substr(start, strSize);
					}
					FoundSLOC(result, strLSLOC, strLSLOCBak, found_block, found_forifwhile,
						found_end, found_type, found_is, found_accept, trunc_flag);
					start = i+1;
					found_is = true;
					continue;
				}
			}
			if (!found_type)
			{
				if (CUtil::FindKeyword(tmp, "type",0,TO_END_OF_STRING,false) !=string::npos)
					found_type = true;
			}
			else
			{
				if (CUtil::FindKeyword(tmp, "record",0,TO_END_OF_STRING,false) != string::npos)
				{
					// The 'if' statement below attempts to resolves the issue with 
					// type Expression is tagged null record;
					// so, ignore this case.
					// Importance: this implementation may not be complete and perfect!
					if (templine.at(templine.length() - 1) != ';')
					{
						strSize = CUtil::TruncateLine(i + 1 - start, strLSLOC.length(), result->lsloc_truncate, trunc_flag);
						if (strSize > 0)
						{
							strLSLOC += line.substr(start, strSize);
							strLSLOCBak += lineBak.substr(start, strSize);
						}
						FoundSLOC(result, strLSLOC, strLSLOCBak, found_block, found_forifwhile,
							found_end, found_type, found_is, found_accept, trunc_flag);
						start = i+1;
						continue;				
					}
				}
			}

			// VN: process 'select...end select;', 'accept ... end accept;'
			//     'record ... end record;' is handled via 'type'
			// select ... end select;  --> only one word statement 'select'
			// accept id... do ... end [id]; --> SLOC starting from 'accept' to 'do'
			// find 'do' only already found 'accept'
			if (CUtil::FindKeyword(tmp, "select",0,TO_END_OF_STRING,false) !=string::npos)
			{
				// found 'select' statement, one SLOC
				strSize = CUtil::TruncateLine(i + 1 - start, strLSLOC.length(), result->lsloc_truncate, trunc_flag);
				if (strSize > 0)
				{
					strLSLOC += line.substr(start, strSize);
					strLSLOCBak += lineBak.substr(start, strSize);
				}
				FoundSLOC(result, strLSLOC, strLSLOCBak, found_block, found_forifwhile,
					found_end, found_type, found_is, found_accept, trunc_flag);
				start = i+1;
				continue;
			}

			if (!found_accept)
			{
				if (CUtil::FindKeyword(tmp, "accept",0,TO_END_OF_STRING,false) !=string::npos)
					found_accept = true;
			}
			else
			{
				if (CUtil::FindKeyword(tmp, "do",0,TO_END_OF_STRING,false) != string::npos)
				{
					//found a SLOC
					strSize = CUtil::TruncateLine(i + 1 - start, strLSLOC.length(), result->lsloc_truncate, trunc_flag);
					if (strSize > 0)
					{
						strLSLOC += line.substr(start, strSize);
						strLSLOCBak += lineBak.substr(start, strSize);
					}
					FoundSLOC(result, strLSLOC, strLSLOCBak, found_block, found_forifwhile,
						found_end, found_type, found_is, found_accept, trunc_flag);
					start = i+1;
				}
			}
		}
	} //for

	tmp = CUtil::TrimString(line.substr(start, i - start));
	strSize = CUtil::TruncateLine(tmp.length(), strLSLOC.length(), result->lsloc_truncate, trunc_flag);
	if (strSize > 0)
	{
		strLSLOC += tmp.substr(0, strSize);
		tmp = CUtil::TrimString(lineBak.substr(start, i - start));
		strLSLOCBak += tmp.substr(0, strSize);
	}
	
	if (tmp == "") 
	{
		found_forifwhile = false;
	}
}
